package com.vincent.template.commons.validation.validators;

import com.google.common.collect.Lists;
import com.vincent.template.commons.validation.constraints.FieldCompare;
import com.vincent.template.utils.FieldUtil;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.ValidationException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Date;
import java.util.List;

/**
 * @author Vincent Wang
 * @date 2020/11/9 17:29
 */
public class FieldCompareValidator implements ConstraintValidator<FieldCompare, Object> {

    private String targetField;
    private String baseField;
    private FieldCompare.Condition condition;
    private FieldCompare.EmptyCase emptyCase;

    @Override
    public void initialize(FieldCompare constraintAnnotation) {
        targetField = constraintAnnotation.targetField();
        baseField = constraintAnnotation.baseField();
        condition = constraintAnnotation.condition();
        emptyCase = constraintAnnotation.emptyCase();
    }

    @Override
    public boolean isValid(Object obj, ConstraintValidatorContext constraintValidatorContext) {
        Class<?> clazz = obj.getClass();
        try {
            Method targetMethod = clazz.getMethod(FieldUtil.getMethodName(targetField));
            Method baseMethod = clazz.getMethod(FieldUtil.getMethodName(baseField));
            Class<?> targetClazz = targetMethod.getReturnType();
            Class<?> baseClazz = baseMethod.getReturnType();
            if (!isSameType(targetClazz, baseClazz)) {
                throw new ValidationException("字段" + targetField + "和" + baseField + "数据类型必须一致");
            }
            Object targetValue = targetMethod.invoke(obj);
            Object baseValue = baseMethod.invoke(obj);
            boolean targetEmpty = !FieldUtil.hasValue(targetValue);
            boolean baseEmpty = !FieldUtil.hasValue(baseValue);
            if (targetEmpty || baseEmpty) {
                return emptyCase(targetEmpty, baseEmpty);
            }
            return compareResult(targetClazz, targetValue, baseValue);
        } catch (Exception e) {
            throw new ValidationException(e.getMessage());
        }
    }

    /**
     * 判断数据类型是否一致
     * @param targetClazz
     * @param baseClazz
     * @return
     */
    private boolean isSameType(Class<?> targetClazz, Class<?> baseClazz) {
        if (int.class.equals(targetClazz) || Integer.class.equals(targetClazz)) {
            return int.class.equals(baseClazz) || Integer.class.equals(baseClazz);
        }
        if (long.class.equals(targetClazz) || Long.class.equals(targetClazz)) {
            return long.class.equals(baseClazz) || Long.class.equals(baseClazz);
        }
        if (float.class.equals(targetClazz) || Float.class.equals(targetClazz)) {
            return float.class.equals(baseClazz) || Float.class.equals(baseClazz);
        }
        if (double.class.equals(targetClazz) || Double.class.equals(targetClazz)) {
            return double.class.equals(baseClazz) || Double.class.equals(baseClazz);
        }
        if (short.class.equals(targetClazz) || Short.class.equals(targetClazz)) {
            return short.class.equals(baseClazz) || Short.class.equals(baseClazz);
        }
        if (byte.class.equals(targetClazz) || Byte.class.equals(targetClazz)) {
            return byte.class.equals(baseClazz) || Byte.class.equals(baseClazz);
        }
        if (char.class.equals(targetClazz) || Character.class.equals(targetClazz)) {
            return char.class.equals(baseClazz) || Character.class.equals(baseClazz);
        }
        if (String.class.equals(targetClazz) ||
                Date.class.equals(targetClazz) ||
                BigDecimal.class.equals(targetClazz) ||
                BigInteger.class.equals(targetClazz)) {
            return targetClazz.equals(baseClazz);
        }
        throw new ValidationException("字段为非法的数据类型，无法比较");
    }

    /**
     * 当有值为空时的结果
     * @param targetEmpty
     * @param baseEmpty
     * @return
     */
    private boolean emptyCase(boolean targetEmpty, boolean baseEmpty) {
        if (FieldCompare.EmptyCase.THROW_EXCEPTION.equals(emptyCase)) {
            List<String> emptyFields = Lists.newArrayList();
            if (targetEmpty) {
                emptyFields.add(targetField);
            }
            if (baseEmpty) {
                emptyFields.add(baseField);
            }
            throw new ValidationException(String.join(",", emptyFields) + "不能为空");
        }
        if (FieldCompare.EmptyCase.SKIP_CHECK.equals(emptyCase)) {
            return true;
        }
        if (targetEmpty && baseEmpty) {
            return FieldCompare.Condition.EQUALS.equals(condition) ||
                    FieldCompare.Condition.GREATER_OR_EQUALS.equals(condition) ||
                    FieldCompare.Condition.LESSER_OR_EQUALS.equals(condition);
        }
        if (FieldCompare.Condition.NOT_EQUALS.equals(condition)) {
            return true;
        }
        boolean greaterCondition = FieldCompare.Condition.GREATER.equals(condition) ||
                FieldCompare.Condition.GREATER_OR_EQUALS.equals(condition);
        boolean lesserCondition = FieldCompare.Condition.LESSER.equals(condition) ||
                FieldCompare.Condition.LESSER_OR_EQUALS.equals(condition);
        if (FieldCompare.EmptyCase.AS_GREATER.equals(emptyCase)) {
            return targetEmpty && greaterCondition || !targetEmpty && lesserCondition;
        }
        if (FieldCompare.EmptyCase.AS_LESSER.equals(emptyCase)) {
            return targetEmpty && lesserCondition || !targetEmpty && greaterCondition;
        }
        return false;
    }

    /**
     * 比较两个值的结果
     * @param targetValue
     * @param baseValue
     * @return
     */
    private boolean compareResult(Class<?> clazz, Object targetValue, Object baseValue) {
        int result = 0;
        if (int.class.equals(clazz) || Integer.class.equals(clazz)) {
            int tv = (int) targetValue;
            int bv = (int) baseValue;
            result = Integer.compare(tv, bv);
        } else if (long.class.equals(clazz) || Long.class.equals(clazz)) {
            long tv = (long) targetValue;
            long bv = (long) baseValue;
            result = Long.compare(tv, bv);
        } else if (float.class.equals(clazz) || Float.class.equals(clazz)) {
            float tv = (float) targetValue;
            float bv = (float) baseValue;
            result = Float.compare(tv, bv);
        } else if (double.class.equals(clazz) || Double.class.equals(clazz)) {
            double tv = (double) targetValue;
            double bv = (double) baseValue;
            result = Double.compare(tv, bv);
        } else if (short.class.equals(clazz) || Short.class.equals(clazz)) {
            short tv = (short) targetValue;
            short bv = (short) baseValue;
            result = Short.compare(tv, bv);
        } else if (byte.class.equals(clazz) || Byte.class.equals(clazz)) {
            byte tv = (byte) targetValue;
            byte bv = (byte) baseValue;
            result = Byte.compare(tv, bv);
        } else if (char.class.equals(clazz) || Character.class.equals(clazz)) {
            char tv = (char) targetValue;
            char bv = (char) baseValue;
            result = Character.compare(tv, bv);
        } else if (String.class.equals(clazz)) {
            String tv = (String) targetValue;
            String bv = (String) baseValue;
            result = tv.compareTo(bv);
        } else if (Date.class.equals(clazz)) {
            Date tv = (Date) targetValue;
            Date bv = (Date) baseValue;
            result = tv.compareTo(bv);
        } else if (BigDecimal.class.equals(clazz)) {
            BigDecimal tv = (BigDecimal) targetValue;
            BigDecimal bv = (BigDecimal) baseValue;
            result = tv.compareTo(bv);
        } else if (BigInteger.class.equals(clazz)) {
            BigInteger tv = (BigInteger) targetValue;
            BigInteger bv = (BigInteger) baseValue;
            result = tv.compareTo(bv);
        }
        return condition.getExpress().decide(result);
    }
}
